AWS Systems Manager ランコマンドの出力を AWS CLI で一括で取得する
コンバンハ、千葉(幸)です。
今回やりたいのはこういったことです。
- マネジメントコンソール上で複数台を対象に AWS Systems Manager ランコマンドを実行した
- 当該コマンドについて、それぞれのターゲットインスタンスからの出力を AWS CLI で一括で取得したい
そもそもの発端は以下エントリを書いたことでした。
ここではAWS-RunPowerShellScript
ドキュメントをランコマンドで実行し、複数台の Windows インスタンスにインストールされた CloudWatch エージェントの番号を出力しています。この時は実行結果をマネジメントコンソール上で 1 台ずつ確認したので、AWS CLI で一括で確認したいと考えました。
で、上記のケースでいえば以下のようなコマンドで実現できました。
$ COMMAND_ID=xxxxxxxx-xxxx-xxxx-xxxxx-xxxxxxxxxxxx $ aws ssm list-command-invocations --command-id $COMMAND_ID --details\ --query "sort_by(CommandInvocations, &InstanceId)[].[InstanceId, InstanceName, Status, CommandPlugins[0].Output]" \ --output text | awk 'BEGIN {print "InstanceId, InstanceName, Status, Output"} { print $1", "$2", "$3", "$4 }' | column -t -s ',' | sed '/^ *$/d' InstanceId InstanceName Status Output i-0xxxxxxxxxxxxxxxx HOSTNAME-A Success 1.247349.0b251399 i-0xxxxxxxxxxxxxxxx HOSTNAME-B Success 1.247348.0b251302 i-0xxxxxxxxxxxxxxxx HOSTNAME-C Success 1.247347.4b250525 i-0xxxxxxxxxxxxxxxx HOSTNAME-D Success 1.247347.6b250880 i-0xxxxxxxxxxxxxxxx HOSTNAME-E Success 1.247349.0b251399 i-0xxxxxxxxxxxxxxxx HOSTNAME-F Success 1.231221.0 ...
aws ssm list-command-invocations
をうまく使うことで大体のケースに対応できたのですが、少し注意点もあったので何パターンかご紹介します。
前提知識
- AWS Systems Manager ランコマンドの出力には「標準出力」と「標準エラー出力」がある
- ランコマンドの出力を AWS CLI で取得する際には以下のコマンドが候補となる
list-command-invocations
で出力を確認するためには--details
オプションを付与する必要があるlist-command-invocations
では標準出力と標準エラー出力が一つの項目として出力されるlist-command-invocations
ではプラグインごとに出力が分かれることがある
このあたりの詳細は以下エントリをご参照ください。
今回はすべてのパターンでlist-command-invocations
を用います。また、取得したいのは標準出力のみであるとします。(細部をカスタマイズすれば標準エラー出力も取得できます。)
実行環境
今回のコマンドは手元の Mac 端末と AWS CloudShell で動作確認をしています。
主要なコマンドのバージョンをそれぞれ記しておきます。
- AWS CLI
- awk
- sed
Mac 端末の場合。
$ aws --version aws-cli/2.9.1 Python/3.9.11 Darwin/22.1.0 exe/x86_64 prompt/off $ awk --version awk version 20200816 $ sed --version #BSD版であることを表すらしい sed: illegal option -- - usage: sed script [-Ealnru] [-i extension] [file ...] sed [-Ealnu] [-i extension] [-e script] ... [-f script_file] ... [file ...]
AWS CloudShell の場合。
$ aws --version aws-cli/2.9.10 Python/3.9.11 Linux/4.14.301-224.520.amzn2.x86_64 exec-env/CloudShell exe/x86_64.amzn.2 prompt/off $ awk --version GNU Awk 4.0.2 Copyright (C) 1989, 1991-2012 Free Software Foundation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. $ sed --version sed (GNU sed) 4.2.2 Copyright (C) 2012 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Written by Jay Fenlason, Tom Lord, Ken Pizzini, and Paolo Bonzini. GNU sed home page: <http://www.gnu.org/software/sed/>. General help using GNU software: <http://www.gnu.org/gethelp/>. E-mail bug reports to: <bug-sed@gnu.org>. Be sure to include the word ``sed'' somewhere in the ``Subject:'' field.
ランコマンドの出力を AWS CLI で一括で取得するパターン
大まかな考え方は変わらないのですが、細部を微妙に変えたパターンを用意しました
- A. 単純に table 出力するパターン
- B. 単純に json 出力するパターン
- C. awk をかませるパターン
- D. awk をかませて S3 からストリーミングダウンロードするパターン
以下の要素によってどのパターンを選ぶべきかが変わります。
- 標準出力に改行が含まれるかどうか
- 標準エラー出力があるかどうか
当てはめてみるとこのようになります。(△
:できるけど他のパターンがおすすめ、◯
:おすすめ)
# | 標準出力 | 標準エラー出力 | A | B | C | D |
---|---|---|---|---|---|---|
1 | 改行なし | あり | △ | ◯ | ||
2 | 改行なし | なし | △ | △ | ◯ | △ |
3 | 改行あり | あり | ◯ | |||
4 | 改行あり | なし | ◯ |
B の json 出力パターンは大抵の場合で使えるので汎用性があります。個人的なお気に入りはパターン D ですが、使い所は少し限られます。また、D パターンはランコマンド実行時の「S3 バケットへの書き込み」を有効にしていることが前提となります。
ランコマンドの実行例
今回は以下のようなランコマンドを実行したケースを想定します。
aws ssm send-command\ --document-name "AWS-RunShellScript"\ --document-version "1"\ --targets '[{"Key":"InstanceIds","Values":["i-02148b21728321265","i-04f6c32b1d9c46916","i-0b14c40e0479d0009"]}]'\ --parameters '{"workingDirectory":[""],"executionTimeout":["3600"],"commands":["#!/bin/bash","curl http://169.254.169.254/latest/meta-data/local-ipv4"]}'\ --timeout-seconds 600 --max-concurrency "50" --max-errors "0"\ --output-s3-bucket-name "chiba-ssm-log" --output-s3-key-prefix "hogehoge"\ --region ap-northeast-1
ドキュメントAWS-RunShellScript
で以下のコマンドを実行する内容です。
#!/bin/bash curl http://169.254.169.254/latest/meta-data/local-ipv4
これにより標準出力には当該インスタンスの IP アドレスが、標準エラー出力には curl のプログレスバーが出力されます。
このランコマンドの結果をaws ssm list-command-invocations
で確認すると以下のような結果が得られます。
% COMMAND_ID=2ca57e61-f070-4803-b008-3050f8c15a5c % aws ssm list-command-invocations --command-id $COMMAND_ID\ --details { "CommandInvocations": [ { "CommandId": "2ca57e61-f070-4803-b008-3050f8c15a5c", "InstanceId": "i-0b14c40e0479d0009", "InstanceName": "ip-172-31-38-78.ap-northeast-1.compute.internal", "Comment": "", "DocumentName": "AWS-RunShellScript", "DocumentVersion": "1", "RequestedDateTime": "2022-12-28T03:18:14.983000+09:00", "Status": "Success", "StatusDetails": "Success", "StandardOutputUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/hogehoge/2ca57e61-f070-4803-b008-3050f8c15a5c/i-0b14c40e0479d0009/awsrunShellScript/0.awsrunShellScript/stdout", "StandardErrorUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/hogehoge/2ca57e61-f070-4803-b008-3050f8c15a5c/i-0b14c40e0479d0009/awsrunShellScript/0.awsrunShellScript/stderr", "CommandPlugins": [ { "Name": "aws:runShellScript", "Status": "Success", "StatusDetails": "Success", "ResponseCode": 0, "ResponseStartDateTime": "2022-12-28T03:18:15.183000+09:00", "ResponseFinishDateTime": "2022-12-28T03:18:15.499000+09:00", "Output": "172.31.38.78\n----------ERROR-------\n % Total % Received % Xferd Average Speed Time Time Time Current\n Dload Upload Total Spent Left Speed\n\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r100 12 100 12 0 0 8510 0 --:--:-- --:--:-- --:--:-- 12000\n", "StandardOutputUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/hogehoge/2ca57e61-f070-4803-b008-3050f8c15a5c/i-0b14c40e0479d0009/awsrunShellScript/0.awsrunShellScript/stdout", "StandardErrorUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/hogehoge/2ca57e61-f070-4803-b008-3050f8c15a5c/i-0b14c40e0479d0009/awsrunShellScript/0.awsrunShellScript/stderr", "OutputS3Region": "ap-northeast-1", "OutputS3BucketName": "chiba-ssm-log", "OutputS3KeyPrefix": "hogehoge/2ca57e61-f070-4803-b008-3050f8c15a5c/i-0b14c40e0479d0009/awsrunShellScript" } ], "ServiceRole": "", "NotificationConfig": { "NotificationArn": "", "NotificationEvents": [], "NotificationType": "" }, "CloudWatchOutputConfig": { "CloudWatchLogGroupName": "", "CloudWatchOutputEnabled": false } }, { ...2台目の実行結果... }, { ...3台目の実行結果... } ] }
↑Output には標準出力と標準エラー出力が連結されて記録されています。
標準エラー出力が含まれないパターン
実行するコマンドを以下のように変更し、標準エラー出力が含まれないパターンのランコマンドも実行しておきます。
#!/bin/bash curl http://169.254.169.254/latest/meta-data/local-ipv4 2> /dev/null
実行結果のイメージはこちらです。標準出力のみになっていることが分かります。
% COMMAND_ID=5431f2a8-1fc2-47b5-aca0-3abc55cac49d % aws ssm list-command-invocations --command-id $COMMAND_ID\ --details { "CommandInvocations": [ { "CommandId": "5431f2a8-1fc2-47b5-aca0-3abc55cac49d", "InstanceId": "i-0a9960e6e67d81a58", "InstanceName": "ip-172-31-32-131.ap-northeast-1.compute.internal", "Comment": "", "DocumentName": "AWS-RunShellScript", "DocumentVersion": "1", "RequestedDateTime": "2022-12-28T04:06:59.740000+09:00", "Status": "Success", "StatusDetails": "Success", "StandardOutputUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/hogehoge/5431f2a8-1fc2-47b5-aca0-3abc55cac49d/i-0a9960e6e67d81a58/awsrunShellScript/0.awsrunShellScript/stdout", "StandardErrorUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/hogehoge/5431f2a8-1fc2-47b5-aca0-3abc55cac49d/i-0a9960e6e67d81a58/awsrunShellScript/0.awsrunShellScript/stderr", "CommandPlugins": [ { "Name": "aws:runShellScript", "Status": "Success", "StatusDetails": "Success", "ResponseCode": 0, "ResponseStartDateTime": "2022-12-28T04:06:59.872000+09:00", "ResponseFinishDateTime": "2022-12-28T04:07:00.004000+09:00", "Output": "172.31.32.131", "StandardOutputUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/hogehoge/5431f2a8-1fc2-47b5-aca0-3abc55cac49d/i-0a996 0e6e67d81a58/awsrunShellScript/0.awsrunShellScript/stdout", "StandardErrorUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/hogehoge/5431f2a8-1fc2-47b5-aca0-3abc55cac49d/i-0a9960 e6e67d81a58/awsrunShellScript/0.awsrunShellScript/stderr", "OutputS3Region": "ap-northeast-1", "OutputS3BucketName": "chiba-ssm-log", "OutputS3KeyPrefix": "hogehoge/5431f2a8-1fc2-47b5-aca0-3abc55cac49d/i-0a9960e6e67d81a58/awsrunShellScript" } ], "ServiceRole": "", "NotificationConfig": { "NotificationArn": "", "NotificationEvents": [], "NotificationType": "" }, "CloudWatchOutputConfig": { "CloudWatchLogGroupName": "", "CloudWatchOutputEnabled": false } }, { ...略... } ] }
↑また、ここでの標準出力には末尾も含めて改行コードが含まれていないことが分かります。
A. 単純に table 出力するパターン
パターン A のコマンド例は以下です。
% aws ssm list-command-invocations --command-id $COMMAND_ID --details\ --query "sort_by(CommandInvocations, &InstanceId)[].[InstanceId, InstanceName, Status, CommandPlugins[0].Output]" \ --output table
標準エラー出力なしパターンのランコマンドの結果を当てはめて実行したイメージは以下です。
% COMMAND_ID=5431f2a8-1fc2-47b5-aca0-3abc55cac49d % aws ssm list-command-invocations --command-id $COMMAND_ID --details\ --query "sort_by(CommandInvocations, &InstanceId)[].[InstanceId, InstanceName, Status, CommandPlugins[0].Output]" \ --output table --------------------------------------------------------------------------------------------------------- | ListCommandInvocations | +---------------------+----------------------------------------------------+----------+-----------------+ | i-097c5c842f42b067a| ip-172-31-38-156.ap-northeast-1.compute.internal | Success | 172.31.38.156 | | i-0a9960e6e67d81a58| ip-172-31-32-131.ap-northeast-1.compute.internal | Success | 172.31.32.131 | +---------------------+----------------------------------------------------+----------+-----------------+
AWS CLI だけで完結しているので分かりやすいと言えば分かりやすいですね。
B. 単純に json 出力するパターン
コマンド例は以下です。
% aws ssm list-command-invocations --command-id $COMMAND_ID --details\ --query "sort_by(CommandInvocations, &InstanceId)[].[InstanceId, InstanceName, Status, CommandPlugins[0].Output]" \ --output json
標準エラー出力ありのランコマンドの実行結果を当てはめた例が以下です。
% COMMAND_ID=2ca57e61-f070-4803-b008-3050f8c15a5c % aws ssm list-command-invocations --command-id $COMMAND_ID --details\ --query "sort_by(CommandInvocations, &InstanceId)[].[InstanceId, InstanceName, Status, CommandPlugins[0].Output]" \ --output json [ [ "i-02148b21728321265", "ip-172-31-40-136.ap-northeast-1.compute.internal", "Success", "172.31.40.136\n----------ERROR-------\n % Total % Received % Xferd Average Speed Time Time Time Current\n Dload Upload Total Spent Left Speed\n\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r100 13 100 13 0 0 10400 0 --:--:-- --:--:-- --:--:-- 13000\n" ], [ "i-04f6c32b1d9c46916", "ip-172-31-42-204.ap-northeast-1.compute.internal", "Success", "172.31.42.204\n----------ERROR-------\n % Total % Received % Xferd Average Speed Time Time Time Current\n Dload Upload Total Spent Left Speed\n\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r100 13 100 13 0 0 9863 0 --:--:-- --:--:-- --:--:-- 13000\n" ], [ "i-0b14c40e0479d0009", "ip-172-31-38-78.ap-northeast-1.compute.internal", "Success", "172.31.38.78\n----------ERROR-------\n % Total % Received % Xferd Average Speed Time Time Time Current\n Dload Upload Total Spent Left Speed\n\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r100 12 100 12 0 0 8510 0 --:--:-- --:--:-- --:--:-- 12000\n" ] ]
お世辞にも見やすいとは言えませんが、改行コードが含まれる場合は他のパターンが対応していない(表示が崩れる)ため、このパターンが一番安牌かと思います。
CommandPlugins の中身が複数ある場合
ここまではCommandPlugins[0].Output
のようにプラグインの1番目のみを表示する前提でコマンドを組んできました。ただ、中には複数中身が存在するものもあります。
ドキュメントAWS-ConfigureAWSPackage
の実行例をみるとこのように 2 つ存在することが分かります。
% COMMAND_ID=1b85a43b-65f8-4f98-94e9-0489c9092a69 % aws ssm list-command-invocations --command-id $COMMAND_ID\ --details { "CommandInvocations": [ { "CommandId": "1b85a43b-65f8-4f98-94e9-0489c9092a69", "InstanceId": "i-0a9960e6e67d81a58", "InstanceName": "ip-172-31-32-131.ap-northeast-1.compute.internal", "Comment": "", "DocumentName": "AWS-ConfigureAWSPackage", "DocumentVersion": "1", "RequestedDateTime": "2022-12-28T04:37:46.562000+09:00", "Status": "Success", "StatusDetails": "Success", "StandardOutputUrl": "", "StandardErrorUrl": "", "CommandPlugins": [ { "Name": "configurePackage", "Status": "Success", "StatusDetails": "Success", "ResponseCode": 0, "ResponseStartDateTime": "2022-12-28T04:37:46.703000+09:00", "ResponseFinishDateTime": "2022-12-28T04:37:54.796000+09:00", "Output": "Initiating arn:aws:ssm:::package/AmazonCloudWatchAgent 1.247357.0b252275 install\nPlugin aws:runShellScript ResultStatus Success\ninstall output: Running sh install.sh\ncreate group cwagent, result: 0\ncreate user cwagent, result: 0\n\nSuccessfully installed arn:aws:ssm:::package/AmazonCloudWatchAgent 1.247357.0b252275\n", "StandardOutputUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/cwagent/1b85a43b-65f8-4f98-94e9-0489c9092a69/i-0a9960e6e67d81a58/awsconfigurePackage/configurePackage/stdout", "StandardErrorUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/cwagent/1b85a43b-65f8-4f98-94e9-0489c9092a69/i-0a9960e6e67d81a58/awsconfigurePackage/configurePackage/stderr", "OutputS3Region": "ap-northeast-1", "OutputS3BucketName": "chiba-ssm-log", "OutputS3KeyPrefix": "cwagent/1b85a43b-65f8-4f98-94e9-0489c9092a69/i-0a9960e6e67d81a58/awsconfigurePackage" }, { "Name": "createDownloadFolder", "Status": "Success", "StatusDetails": "Success", "ResponseCode": 0, "ResponseStartDateTime": "2022-12-28T04:37:46.703000+09:00", "ResponseFinishDateTime": "2022-12-28T04:37:46.703000+09:00", "Output": "Step execution skipped due to unsatisfied preconditions: '\"StringEquals\": [platformType, Windows]'. Step name: createDownloadFolder", "StandardOutputUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/cwagent/1b85a43b-65f8-4f98-94e9-0489c9092a69/i-0a9960e6e67d81a58/awsrunPowerShellScript/0.createDownloadFolder/stdout", "StandardErrorUrl": "https://s3.ap-northeast-1.amazonaws.com/chiba-ssm-log/cwagent/1b85a43b-65f8-4f98-94e9-0489c9092a69/i-0a9960e6e67d81a58/awsrunPowerShellScript/0.createDownloadFolder/stderr", "OutputS3Region": "ap-northeast-1", "OutputS3BucketName": "chiba-ssm-log", "OutputS3KeyPrefix": "cwagent/1b85a43b-65f8-4f98-94e9-0489c9092a69/i-0a9960e6e67d81a58/awsrunPowerShellScript" } ], "ServiceRole": "", "NotificationConfig": { "NotificationArn": "", "NotificationEvents": [], "NotificationType": "" }, "CloudWatchOutputConfig": { "CloudWatchLogGroupName": "", "CloudWatchOutputEnabled": false } }, { ...略... } ] }
それぞれの出力を取得したい場合は、CommandPlugins[0].Output
としていた部分をCommandPlugins[].Output
に変更します。
% COMMAND_ID=1b85a43b-65f8-4f98-94e9-0489c9092a69 % aws ssm list-command-invocations --command-id $COMMAND_ID --details\ --query "sort_by(CommandInvocations, &InstanceId)[].[InstanceId, InstanceName, Status, CommandPlugins[].Output]" \ --output json [ [ "i-097c5c842f42b067a", "ip-172-31-38-156.ap-northeast-1.compute.internal", "Success", [ "Initiating arn:aws:ssm:::package/AmazonCloudWatchAgent 1.247357.0b252275 install\nPlugin aws:runShellScript ResultStatus Success\ninstall output: Running sh install.sh\ncreate group cwagent, result: 0\ncreate user cwagent, result: 0\n\nSuccessfully installed arn:aws:ssm:::package/AmazonCloudWatchAgent 1.247357.0b252275\n", "Step execution skipped due to unsatisfied preconditions: '\"StringEquals\": [platformType, Windows]'. Step name: createDownloadFolder" ] ], [ "i-0a9960e6e67d81a58", "ip-172-31-32-131.ap-northeast-1.compute.internal", "Success", [ "Initiating arn:aws:ssm:::package/AmazonCloudWatchAgent 1.247357.0b252275 install\nPlugin aws:runShellScript ResultStatus Success\ninstall output: Running sh install.sh\ncreate group cwagent, result: 0\ncreate user cwagent, result: 0\n\nSuccessfully installed arn:aws:ssm:::package/AmazonCloudWatchAgent 1.247357.0b252275\n", "Step execution skipped due to unsatisfied preconditions: '\"StringEquals\": [platformType, Windows]'. Step name: createDownloadFolder" ] ] ]
「CommandPlugins の中身が複数ある場合」に対応しているのもパターン B のみとなります。
C. awk をかませるパターン
コマンド例は以下です。
% aws ssm list-command-invocations --command-id $COMMAND_ID --details\ --query "sort_by(CommandInvocations, &InstanceId)[].[InstanceId, InstanceName, Status, CommandPlugins[0].Output]" \ --output text | awk 'BEGIN {print "InstanceId, InstanceName, Status, Output"} { print $1", "$2", "$3", "$4 }' | column -t -s ','
使えるケースが「標準出力に改行なし」かつ「標準エラー出力なし」とパターン A と被るのですが、出力結果がおしゃれなのでこちらのパターンの方が好きです。
実行結果のイメージは以下の通り。
% COMMAND_ID=5431f2a8-1fc2-47b5-aca0-3abc55cac49d % aws ssm list-command-invocations --command-id $COMMAND_ID --details\ --query "sort_by(CommandInvocations, &InstanceId)[].[InstanceId, InstanceName, Status, CommandPlugins[0].Output]" \ --output text | awk 'BEGIN {print "InstanceId, InstanceName, Status, Output"} { print $1", "$2", "$3", "$4 }' | column -t -s ',' InstanceId InstanceName Status Output i-097c5c842f42b067a ip-172-31-38-156.ap-northeast-1.compute.internal Success 172.31.38.156 i-0a9960e6e67d81a58 ip-172-31-32-131.ap-northeast-1.compute.internal Success 172.31.32.131
大まかに以下の内容を行なっています。
- テキスト形式で awk に値を渡す
- awk の BEGIN でヘッダーを出力
- awk で引数をカンマとスペース(
", "
)で区切って出力 - column で 区切り文字にカンマを指定してテーブル形式に変換
標準出力の末尾に改行が含まれる場合
Cパターンを使えるケースとして「標準出力に改行なし」を挙げましたが、「改行が末尾にある」場合であれば一工夫すれば使えます。
例えば今回の出力は172.31.38.156
などでしたが、これが172.31.38.156\n
などであったケースです。末尾に改行が含まれる場合、パターンCの出力結果に改行が挟まることになります。
以下のようにsed '/^ *$/d'
で空白行を削除してあげれば綺麗に出力されます。
$ aws ssm list-command-invocations --command-id $COMMAND_ID --details\ --query "sort_by(CommandInvocations, &InstanceId)[].[InstanceId, InstanceName, Status, CommandPlugins[0].Output]" \ --output text | awk 'BEGIN {print "InstanceId, InstanceName, Status, Output"} { print $1", "$2", "$3", "$4 }' | column -t -s ',' | sed '/^ *$/d'
冒頭で載せた「CloudWatch エージェントの番号を取得するランコマンド」の例でも、この末尾の改行を取り除くパターンを用いています。
D. awk をかませて S3 からストリーミングダウンロードするパターン
ここまでのパターンと異なり、ランコマンド実行時の「S3 バケットへの書き込み」が有効になっていることが前提です。
コマンドの例は以下です。
$ aws ssm list-command-invocations --command-id $COMMAND_ID \ --query "sort_by(CommandInvocations, &InstanceId)[].[InstanceId, InstanceName, Status, StandardOutputUrl]" \ --output text | awk 'BEGIN {print "InstanceId, InstanceName, Status, Output"} { "echo "$4" | sed -e \"s/.s3.*.com\//\" -e \"s/https/s3/\"" | getline s3uri "aws s3 cp "s3uri" -" | getline output print $1", "$2", "$3", "output }' | column -t -s ','
標準エラー出力が含まれるランコマンドの実行結果を当てはめてみるとこのような感じに。
% COMMAND_ID=2ca57e61-f070-4803-b008-3050f8c15a5c % aws ssm list-command-invocations --command-id $COMMAND_ID \ --query "sort_by(CommandInvocations, &InstanceId)[].[InstanceId, InstanceName, Status, StandardOutputUrl]" \ --output text | awk 'BEGIN {print "InstanceId, InstanceName, Status, Output"} { "echo "$4" | sed -e \"s/.s3.*.com\//\" -e \"s/https/s3/\"" | getline s3uri "aws s3 cp "s3uri" -" | getline output print $1", "$2", "$3", "output }' | column -t -s ',' InstanceId InstanceName Status Output i-02148b21728321265 ip-172-31-40-136.ap-northeast-1.compute.internal Success 172.31.40.136 i-04f6c32b1d9c46916 ip-172-31-42-204.ap-northeast-1.compute.internal Success 172.31.42.204 i-0b14c40e0479d0009 ip-172-31-38-78.ap-northeast-1.compute.internal Success 172.31.38.78
AWS CloudShell で実行した場合は以下のメッセージが出力されましたが、挙動としては問題ありませんでした。
awk: cmd. line:2: warning: escape sequence `\/' treated as plain `/'
このコマンドで実行している内容は大まかに以下です。
- テキスト形式で awk に値を渡す
- 出力そのものでなく出力の S3 オブジェクト URL を渡している
- awk の BEGIN でヘッダーを出力
- awk 内で 4 つ目の引数(オブジェクト URL)を sed で S3 URL に変換し、変数 s3uri に格納
- awk 内で S3 URL を用いて aws s3 cp によるストリーミングダウンロードを実行し、変数 output に格納
- awk で 1~3 の引数と変数 output をカンマとスペース(
", "
)で区切って出力 - column で 区切り文字にカンマを指定してテーブル形式に変換
当初はaws ssm list-command-invocations
の--details
オプションで Output の詳細が出力できることに気づかなかったので、このパターンが唯一の手法かと勘違いしていました。
awk でこんな風にゴニョゴニョできるんだなということに気づけたので、個人的にはお気に入りのパターンです。
sed で S3 のオブジェクト URL を S3 URL に変換する
aws s3 cp コマンドでは、aws s3 cp <S3 URI> -
のように指定することでストリーミングダウンロード(オブジェクトそのものはダウンロードせず中身のみ取得する)ができます。
ここで指定する S3 URI はs3://
から始まる S3 URL である必要があります。aws ssm list-command-invocations
で得られる URL はパス形式のオブジェクト URL であるため、sed で変換をかけています。
通常実行する際は以下のコマンドで実現できます。
% URL=https://s3.ap-northeast-1.amazonaws.com/{バケット名}/{プレフィックス}/{オブジェクト名} % echo $URL | sed -e 's/s3.*.com\///' -e 's/https/s3/' s3://{バケット名}/{プレフィックス}/{オブジェクト名}
awk の中で sed を用いる際には'
でなく"
を用いる必要があり、かつそれをエスケープしてあげる必要がありました。それに伴い少し構成を変えています。
# 普通にsedを行う場合 sed -e 's/s3.*.com\///' -e 's/https/s3/' # awkの中で用いる場合 sed -e \"s/.s3.*.com\//\" -e \"s/https/s3/\"
ちなみに仮想ホスティング形式の URL を S3 URL に変換したい場合は以下のようにすればよさそうです。
% URL2=https://{バケット名}.s3.ap-northeast-1.amazonaws.com/{プレフィックス}/{オブジェクト名} % echo $URL2 | sed -e 's/.s3.*.com//' -e 's/https/s3/' s3://{バケット名}/{プレフィックス}/{オブジェクト名}
余談:コマンド実行も AWS CLI で行いたい場合
ここまでは「マネジメントコンソールで実行したランコマンドの結果を AWS CLI で確認する」前提で記載していましたが、ランコマンドの実行から AWS CLI で行いたいこともあるでしょう。
ランコマンドの実行はaws ssm send-command
で行います。その戻り値は以下のようなものなので……
{ "Command": { "CommandId": "92853adf-ba41-4cd6-9a88-142d1EXAMPLE", "DocumentName": "AWS-RunShellScript", "DocumentVersion": "", "Comment": "echo HelloWorld", "ExpiresAfter": 1550181014.717, "Parameters": { "commands": [ "echo HelloWorld" ] }, ...
こんな感じでランコマンド ID が取得できそうです。
COMMAND_ID=$(aws ssm send-command {なんらかコマンドに応じたパラメータ} \ --query 'Command.CommandId' --output text)
終わりに
AWS Systems Manager ランコマンドの出力を AWS CLI で一括で取得する、という内容でした。
標準出力に改行が含まれているかどうか、標準エラー出力が存在しているかどうかによって有効なパターンが異なりますので、用途に合ったものを選択してください。もちろん細部はカスタマイズすることも可能です。
今回は見送りましたが、aws ssm get-command-invocation
であれば標準出力と標準エラー出力が分かれて出力される、という違いもあります。そういった部分も含めてこのエントリが何らか参考になれば幸いです。
以上、 チバユキ (@batchicchi) がお送りしました。
参考
awk でゴニョゴニョする手法は以下エントリの内容で知り、参考にしました。